import * as del from "del";
import * as fs from "fs";
import * as moment from "moment";
import * as ora from "ora";
import * as path from "path";
import * as util from "util";
import * as webpack from "webpack";
import devCfg from "../../pack/cfg/dev";
import testCfg from "../../pack/cfg/test";
import prodCfg from "../../pack/cfg/prod";
import { ITask, task, TaskChain } from "../../util";
import { assertConfigIsSet, downloadDllCfg } from "./create";
import { Manifest } from "./manifest";

const boxen = require("boxen");
const readdir = util.promisify(fs.readdir);
const writeFile = util.promisify(fs.writeFile);

const BUILD_CONFIG_LIST = {
  dev: devCfg, // for dev server
  test: testCfg, // for test build
  prod: prodCfg // for prod build
};

export class WebAsyncPackError extends Error {}

export class Packer extends TaskChain {
  cfg: webpack.Configuration;
  dll: any;

  constructor(mode: "dev" | "test" | "prod") {
    super();
    const cfg = BUILD_CONFIG_LIST[mode];
    this.cfg = {
      ...cfg,
      stats: {
        colors: false
      }
    };
  }

  @task("检查配置项")
  async assertConfigIsSet() {
    return assertConfigIsSet(["gitlab.privateToken"]);
  }

  @task("下载 DLL 配置")
  async downloadDllCfg() {
    const dll = await downloadDllCfg();
    this.cfg.externals = dll2externals(dll);
  }

  @task("清除上次构建")
  async cleanupPrevResult() {
    await del(this.cfg.output!.path!);
  }

  @task("执行 webpack")
  async webpack() {
    try {
      await webAsyncPack(this.cfg);
    } catch (e) {
      let msg = `\n✖ Some errors occurred:`.red;
      msg += "\n" + e.message;
      throw new Error(msg);
    }
  }

  @task("整理资源信息")
  async collectOutInfo() {
    const info = {
      ...Manifest.getInstance().cfg,
      js: {
        edit: "edit.js",
        view: "view.js"
      },
      res: []
    };
    const dist = path.resolve(process.cwd(), "dist");
    const files = await readdir(dist);
    info.res = files.filter(n => n !== "edit.js" && n !== "view.js") as any;
    writeFile(path.resolve(dist, "manifest.json"), JSON.stringify(info, null, 2), {
      encoding: "utf8"
    });
  }

  async enterTask(t: ITask): Promise<void> {
    t.extra.spinner = ora(t.name);
    t.extra.spinner.start();
  }

  async leaveTask(t: ITask): Promise<void> {
    t.extra.spinner.succeed(t.name);
  }

  async leaveTaskError(t: ITask, e: Error): Promise<void> {
    t.extra.spinner.stop();
    console.log("\n" + "✘ ".red + t.name + ":\n");
    console.log(
      e.message
        .split("\n")
        .map(l => "  " + l)
        .join("\n")
    );
    if (e.stack) {
      console.log(`\n${"Call Stack:".red.bold}\n` + e.stack + "\n");
    }
    console.log(
      boxen(
        "It looks like a bug? Reporting it to http://git.dev.qianmi.com/OF2764/xwidget-cli/issues",
        { borderStyle: "double-single" }
      ).yellow
    );
  }

  async done(hasFailures: boolean): Promise<void> {
    if (hasFailures) return;
    console.log(`\n🎉  构建成功\n`);
  }
}

export const webAsyncPack = async (cfg: webpack.Configuration) => {
  cfg.externals = {
    ...(cfg.externals as any),
    xKit: "xDllContext.xKit",
    iflux: "xDllContext.iflux",
    react: "xDllContext.React",
    antd: "antd",
    immutable: "xDllContext.Immutable",
    "x-site-integrate": "window.xSiteIntegrate",
    "react-dom": "xDllContext.ReactDom"
  };
  return new Promise((resolve, reject) => {
    webpack(cfg, (err, stats) => {
      if (err) {
        reject(err);
      } else if (stats.hasErrors()) {
        reject(new WebAsyncPackError(stats.toString()));
      } else {
        resolve();
      }
    });
  });
};

export const dll2externals = (dll: any) => {
  const externals: any = {};
  Object.keys(dll).forEach(k => {
    externals[k] = dll[k][1];
  });
  return externals;
};

export const pack = async (mode: "dev" | "test" | "prod", externals: any) => {
  let cfg = BUILD_CONFIG_LIST[mode];
  cfg = {
    ...cfg,
    stats: {
      colors: false
    }
  };
  await del(cfg.output!.path!);
  cfg.externals = externals;
  await webAsyncPack(cfg);
};

export const packCmd = async (mode: "dev" | "test" | "prod") => {
  await new Packer(mode).run();
};
